import type { IncomingHttpHeaders } from 'node:http'
// 复用HttpSession的接口定义，确保兼容性
import type { HttpRequestOptions, HttpResponse, IHttpSession } from './http-session'
import { URL } from 'node:url'
import { generateRandomFingerprint } from '@shared/utils/fingerprint'

import { Agent, ProxyAgent } from 'undici'

import { getConfig } from '../config/index'

// 重新导出接口
export type { HttpRequestOptions, HttpResponse }

/**
 * 高性能HTTP会话（使用undici）
 * - 内置连接池复用
 * - 更高效的并发处理
 * - 更低的资源占用
 */
export class HttpSessionV2 implements IHttpSession {
  private agent: Agent | ProxyAgent
  private cookieJar: Map<string, string> = new Map()
  private lastRequestAt = 0
  private minIntervalMs: number
  private defaultHeaders: Record<string, string>
  private currentProxy: { server: string, username?: string, password?: string } | null = null

  private buildProxyUrl(proxy: { server: string, username?: string, password?: string }): string {
    let server = String(proxy.server || '').trim()
    // 若未包含协议，默认使用 http://
    if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(server))
      server = `http://${server}`
    const u = new URL(server)
    if (proxy.username) {
      u.username = encodeURIComponent(proxy.username)
      u.password = encodeURIComponent(proxy.password || '')
    }
    return u.toString()
  }

  constructor(userHeaders?: Record<string, string>, proxy?: { server: string, username?: string, password?: string }) {
    const cfg = getConfig()
    this.minIntervalMs = Math.max(0, Math.floor((cfg.network.global_min_interval_seconds || 0) * 1000))
    const fp = generateRandomFingerprint()

    this.defaultHeaders = {
      'User-Agent': (userHeaders && userHeaders['User-Agent']) ? userHeaders['User-Agent'] : fp.userAgent,
      'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
      'Accept-Language': fp.acceptLanguage,
      'Content-Type': 'application/x-www-form-urlencoded',
      ...(fp.secChUa ? { 'sec-ch-ua': fp.secChUa } : {}),
      ...(fp.secChUaMobile ? { 'sec-ch-ua-mobile': fp.secChUaMobile } : {}),
      ...(fp.secChUaPlatform ? { 'sec-ch-ua-platform': fp.secChUaPlatform } : {}),
      ...userHeaders,
    }

    // 创建undici Agent（自动管理连接池）
    // 如果使用代理，使用 ProxyAgent 包装 Agent
    const maxConnections = cfg.network?.max_connections_per_host || 10
    const baseAgent = new Agent({
      connections: maxConnections, // 每个域名最大连接数
      pipelining: 1, // HTTP/1.1 流水线
      allowH2: true, // 允许HTTP/2
    })

    // 设置代理
    if (proxy && proxy.server) {
      this.currentProxy = proxy
      // 构建代理URL（支持无协议与认证注入）
      const proxyUrl = this.buildProxyUrl(proxy)
      this.agent = new ProxyAgent(proxyUrl)
    }
    else {
      this.agent = baseAgent
    }
  }

  /**
   * 获取当前使用的代理信息
   */
  getCurrentProxy(): { server: string, username?: string, password?: string } | null {
    return this.currentProxy
  }

  /**
   * 将当前代理加入黑名单
   */
  banCurrentProxy(reason?: string): boolean {
    if (!this.currentProxy)
      return false
    try {
      // 动态导入避免循环依赖
      // eslint-disable-next-line ts/no-require-imports
      const { updateConfig } = require('../config/index')
      const config = getConfig()
      if (!config?.proxy?.enabled)
        return false

      const ttlMin: number = Math.max(1, Number(config.proxy.blacklist_ttl_minutes || 60))
      const entry = { server: this.currentProxy.server, until: Date.now() + ttlMin * 60_000, reason }
      const blacklist = Array.isArray(config.proxy.temp_blacklist) ? config.proxy.temp_blacklist.slice() : []
      blacklist.push(entry)
      updateConfig({ proxy: { temp_blacklist: blacklist } as any }).catch(() => {})
      return true
    }
    catch {
      return false
    }
  }

  /**
   * 发送请求（自动附加 Cookie / 最小间隔 / 解析 Set-Cookie）
   */
  async request(opts: HttpRequestOptions): Promise<HttpResponse> {
    await this.ensureMinInterval()

    const url = new URL(opts.url)
    const headers: Record<string, string> = { ...this.defaultHeaders, ...(opts.headers || {}) }

    // 合并 Cookie
    const cookieHeader = this.getCookieHeader()
    if (cookieHeader)
      headers.Cookie = cookieHeader

    // Referer（如提供）
    if (opts.referer)
      headers.Referer = opts.referer

    // 构建请求选项
    const requestOptions: any = {
      method: opts.method || 'GET',
      headers,
      body: opts.body,
      maxRedirections: 5,
      timeout: opts.timeoutMs || Math.max(5000, getConfig().network.request_timeout_seconds * 1000),
    }

    // 如果使用 per-request 代理，需要创建新的 ProxyAgent
    // 注意：undici 的 ProxyAgent 需要在构造时指定，不能在请求时动态设置
    // 如果请求时提供了不同的代理，需要重新创建 Agent（这里简化处理，优先使用构造函数中的代理）
    const proxyCandidate = (opts as any)?.proxy || this.currentProxy
    if (proxyCandidate && proxyCandidate.server && proxyCandidate.server !== this.currentProxy?.server) {
      // 如果请求时提供了不同的代理，记录警告（当前实现不支持动态切换代理）
      console.warn('[HttpSessionV2] per-request proxy is not supported, using constructor proxy')
    }

    try {
      // undici的request方法需要origin和path分开
      // 注意：undici不支持maxRedirections，需要手动处理重定向
      const requestOpts: any = {
        origin: `${url.protocol}//${url.host}`,
        path: url.pathname + (url.search || ''),
        method: requestOptions.method,
        headers: requestOptions.headers,
      }

      // 添加body（如果有）
      if (requestOptions.body) {
        requestOpts.body = typeof requestOptions.body === 'string'
          ? requestOptions.body
          : Buffer.from(requestOptions.body)
      }

      // 添加超时
      if (requestOptions.timeout) {
        const controller = new AbortController()
        setTimeout(() => controller.abort(), requestOptions.timeout)
        requestOpts.signal = controller.signal
      }

      // 代理已通过 ProxyAgent 在构造函数中配置，无需在请求时设置

      // 手动处理重定向（最多5次）
      let response: any
      let redirectCount = 0
      const maxRedirects = 5
      let currentUrl = url

      while (redirectCount <= maxRedirects) {
        requestOpts.origin = `${currentUrl.protocol}//${currentUrl.host}`
        requestOpts.path = currentUrl.pathname + (currentUrl.search || '')

        response = await this.agent.request(requestOpts)

        // 检查是否需要重定向（301, 302, 303, 307, 308）
        const status = response.statusCode
        const location = response.headers.location

        if (status >= 300 && status < 400 && location && redirectCount < maxRedirects) {
          redirectCount++

          // 消耗当前响应的body（避免内存泄漏）
          try {
            await response.body.dump()
          }
          catch {
            // 忽略错误
          }

          // 解析重定向URL
          try {
            const locationStr = Array.isArray(location) ? location[0] : location
            const redirectUrl = new URL(locationStr, `${currentUrl.protocol}//${currentUrl.host}`)
            currentUrl = redirectUrl
            // 更新请求URL
            requestOpts.origin = `${redirectUrl.protocol}//${redirectUrl.host}`
            requestOpts.path = redirectUrl.pathname + (redirectUrl.search || '')

            // 对于POST请求的重定向，通常改为GET（303, 307除外）
            if (requestOpts.method === 'POST' && (status === 301 || status === 302)) {
              requestOpts.method = 'GET'
              delete requestOpts.body
            }

            continue
          }
          catch {
            // URL解析失败，停止重定向
            break
          }
        }
        else {
          // 不需要重定向，退出循环
          break
        }
      }

      // 解析 Set-Cookie
      const setCookies = response.headers['set-cookie']
      if (setCookies) {
        const cookieArray = Array.isArray(setCookies) ? setCookies : [setCookies]
        this.mergeSetCookies(cookieArray.map(String))
      }

      const body = await response.body.text()

      this.lastRequestAt = Date.now()

      return {
        status: response.statusCode,
        headers: response.headers as IncomingHttpHeaders,
        body,
        url: opts.url,
      }
    }
    catch (error: any) {
      const msg = String(error?.message || error)
      console.warn('[HttpSessionV2] request error:', msg)
      // 在代理链路下，某些上游只使用 LF 或返回非 HTTP/1.1 前言，undici 解析会报错
      // 命中典型解析错误时，回退到 v1 的 Node http 实现以提高兼容性
      const shouldFallback = /Missing expected CR after response line|HPE_INVALID|Invalid HTTP|ERR_INVALID_HTTP_RESPONSE/i.test(msg)
      const alreadyRetried = (opts as any)._v2FallbackTried === true
      if ((this.currentProxy || (opts as any)?.proxy) && shouldFallback && !alreadyRetried) {
        try {
          ;(opts as any)._v2FallbackTried = true
          // 动态导入避免循环依赖
          // eslint-disable-next-line ts/no-require-imports
          const { HttpSession } = require('./http-session')
          const fallback = new HttpSession(this.defaultHeaders, this.currentProxy || (opts as any)?.proxy || undefined)
          // 透传已合并的 headers（包含 Cookie）
          const resp = await fallback.request({ ...opts, headers })
          // 将回退响应中的 Set-Cookie 合并回当前会话
          const sc = (resp.headers as any)['set-cookie']
          if (sc) {
            const arr = Array.isArray(sc) ? sc : [sc]
            this.mergeSetCookies(arr.map(String))
          }
          this.lastRequestAt = Date.now()
          return resp
        }
        catch (e) {
          console.warn('[HttpSessionV2] fallback to HttpSession failed:', (e as any)?.message || String(e))
        }
      }
      throw error
    }
  }

  /**
   * 简化的 POST 表单提交
   */
  async postForm(url: string, form: Record<string, string>, referer?: string): Promise<HttpResponse> {
    const body = new URLSearchParams(form).toString()
    const headers: Record<string, string> = { 'Content-Type': 'application/x-www-form-urlencoded' }

    // 如果提供了 referer，提取 origin（用于 POST 请求，某些服务器需要 Origin 头）
    if (referer) {
      try {
        const refererUrl = new URL(referer)
        headers.Origin = `${refererUrl.protocol}//${refererUrl.host}`
      }
      catch {
        // 忽略 URL 解析错误
      }
    }

    return await this.request({ method: 'POST', url, body, referer, headers })
  }

  /**
   * 关闭连接池
   */
  async close(): Promise<void> {
    await this.agent.close()
  }

  private async ensureMinInterval(): Promise<void> {
    if (this.minIntervalMs <= 0)
      return
    const now = Date.now()
    const elapsed = now - this.lastRequestAt
    if (elapsed < this.minIntervalMs)
      await new Promise(r => setTimeout(r, this.minIntervalMs - elapsed))
  }

  private getCookieHeader(): string {
    if (this.cookieJar.size === 0)
      return ''
    return Array.from(this.cookieJar.entries()).map(([k, v]) => `${k}=${v}`).join('; ')
  }

  private mergeSetCookies(setCookies: string[]): void {
    for (const sc of setCookies) {
      // 只解析 name=value；忽略属性
      const semi = sc.indexOf(';')
      const pair = semi >= 0 ? sc.slice(0, semi) : sc
      const eq = pair.indexOf('=')
      if (eq > 0) {
        const name = pair.slice(0, eq).trim()
        const value = pair.slice(eq + 1).trim()
        if (name && value)
          this.cookieJar.set(name, value)
      }
    }
  }
}
